// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <fbl/auto_call.h>
#include <lib/fdio/spawn.h>
#include <lib/zx/process.h>
#include <zircon/processargs.h>
#include <zircon/process.h>
#include <zxtest/zxtest.h>

#include "utils.h"

const char* ExternalThread::program_name_ = nullptr;
const char* ExternalThread::helper_flag_ = "futex-owner-helper";

bool WaitFor(zx_duration_t timeout, WaitFn wait_fn) {
  constexpr zx_duration_t WAIT_POLL_INTERVAL = ZX_MSEC(1);

  ZX_ASSERT((timeout >= 0) && (timeout <= ZX_SEC(30)));
  zx_time_t deadline = zx_deadline_after(timeout);

  while (!wait_fn()) {
    if (zx_clock_get_monotonic() > deadline) {
      return false;
    }
    zx_nanosleep(WAIT_POLL_INTERVAL);
  }

  return true;
}

zx_koid_t CurrentThreadKoid() {
  zx_info_handle_basic info;
  zx_status_t res = zx_object_get_info(zx_thread_self(), ZX_INFO_HANDLE_BASIC, &info, sizeof(info),
                                       nullptr, nullptr);
  ZX_ASSERT(res == ZX_OK);
  return info.koid;
}

zx_status_t Event::Wait(zx_duration_t timeout) {
  zx_time_t deadline =
      (timeout == ZX_TIME_INFINITE) ? ZX_TIME_INFINITE : zx_deadline_after(timeout);

  while (signaled_.load(fbl::memory_order_relaxed) == 0) {
    zx_status_t res = zx_futex_wait(&signaled_, 0, ZX_HANDLE_INVALID, deadline);
    if ((res != ZX_OK) && (res != ZX_ERR_BAD_STATE)) {
      return res;
    }
  }

  return ZX_OK;
}

void Event::Signal() {
  if (signaled_.load(fbl::memory_order_relaxed) == 0) {
    signaled_.store(1, fbl::memory_order_relaxed);
    zx_futex_wake(&signaled_, UINT32_MAX);
  }
}

void Event::Reset() { signaled_.store(0, fbl::memory_order_relaxed); }

void Thread::Reset() {
  handle_.reset();
  koid_ = ZX_KOID_INVALID;
  SetState(State::WAITING_TO_START);
  thunk_ = nullptr;
  started_evt_.Reset();
  stop_evt_.Reset();
}

void Thread::Start(const char* name, Thunk thunk) {
  ZX_ASSERT(static_cast<bool>(thunk_) == false);

  thunk_ = std::move(thunk);

  auto internal_thunk = [](void* ctx) -> int {
    auto t = reinterpret_cast<Thread*>(ctx);

    // Create a clone of the zx_thread_self handle.  This handle is owned by
    // the runtime, not owned by us.  The runtime will automatically close
    // this handle when the thread exits, invalidating it in the process.
    // If we want to be able to do things like test to see if a thread state
    // has reached DEAD, we need to make our own handle to hold onto.  Do so
    // now.
    //
    // Success or fail, make sure we flag ourselves as started before moving
    // on.  We don't want to hold up the test framework.  They will discover
    // that we failed to start when they check our handle and discover that
    // it failed to duplicate.
    zx::unowned_thread thread_self(zx_thread_self());
    int ret = static_cast<int>(thread_self->duplicate(ZX_RIGHT_SAME_RIGHTS, &(t->handle_)));
    t->koid_ = CurrentThreadKoid();
    t->started_evt_.Signal();

    if (ret == static_cast<int>(ZX_OK)) {
      t->SetState(State::RUNNING);
      ret = t->thunk_();
    }

    t->SetState(State::WAITING_TO_STOP);
    t->stop_evt_.Wait(ZX_TIME_INFINITE);
    t->SetState(State::STOPPED);
    return ret;
  };

  int res = thrd_create_with_name(&thread_, internal_thunk, this, name);
  ASSERT_EQ(res, 0);
  ASSERT_OK(started_evt_.Wait(THREAD_TIMEOUT));
  ASSERT_TRUE(handle_.is_valid());
  ASSERT_NE(koid_, ZX_KOID_INVALID);
}

zx_status_t Thread::Stop() {
  if (!handle_.is_valid()) {
    return ZX_ERR_BAD_STATE;
  }

  stop_evt_.Signal();

  zx_time_t deadline = zx_deadline_after(THREAD_TIMEOUT);
  while (state() != State::STOPPED) {
    if (zx_clock_get_monotonic() > deadline) {
      return ZX_ERR_TIMED_OUT;
    }
    zx_nanosleep(THREAD_POLL_INTERVAL);
  }

  thrd_join(thread_, nullptr);
  Reset();

  return ZX_OK;
}

zx_status_t Thread::GetRunState(uint32_t* run_state) const {
  ZX_DEBUG_ASSERT(run_state != nullptr);
  if (!handle_.is_valid()) {
    return ZX_ERR_BAD_STATE;
  }

  zx_info_thread_t info;
  zx_status_t res = handle_.get_info(ZX_INFO_THREAD, &info, sizeof(info), nullptr, nullptr);
  if (res != ZX_OK) {
    return res;
  }

  *run_state = info.state;
  return ZX_OK;
}

int ExternalThread::DoHelperThread() {
  // Get our channel to our parent from our environment.
  zx::channel remote(zx_take_startup_handle(PA_HND(PA_USER0, 0)));
  if (!remote.is_valid()) {
    return -__LINE__;
  }

  // Duplicate our thread handle.
  zx::unowned_thread cur_thread(zx_thread_self());
  zx::thread thread_copy;
  if (cur_thread->duplicate(ZX_RIGHT_SAME_RIGHTS, &thread_copy) != ZX_OK) {
    return -__LINE__;
  }

  // Send a copy of our thread handle back to our our parent.
  if (zx_handle_t leaked = thread_copy.release();
      remote.write(0, nullptr, 0, &leaked, 1) != ZX_OK) {
    return -__LINE__;
  }

  // Block until our parent closes our control channel, then exit.  Do not
  // block forever... If the worst happens, we don't want to be leaking
  // processes in our test environment.  For now, waiting 2 minutes seems like
  // a Very Long Time to wait for our parent to give us the all clear.
  constexpr zx::duration TIMEOUT(ZX_SEC(120));
  zx_status_t wait_res;
  wait_res = remote.wait_one(ZX_CHANNEL_PEER_CLOSED, zx::deadline_after(TIMEOUT), nullptr);

  return (wait_res == ZX_OK) ? 0 : -__LINE__;
}

void ExternalThread::Start() {
  auto on_failure = fbl::MakeAutoCall([this]() { Stop(); });

  // Make sure that we have a program name and have not already started.
  ASSERT_NOT_NULL(program_name_);
  ASSERT_EQ(external_thread_.get(), ZX_HANDLE_INVALID);
  ASSERT_EQ(control_channel_.get(), ZX_HANDLE_INVALID);

  // Create the channel we will use for talking to our external thread.
  zx::channel local, remote;
  ASSERT_OK(zx::channel::create(0, &local, &remote));

  const char* args[] = {program_name_, helper_flag_, nullptr};
  struct fdio_spawn_action transfer_channel_action = {
      .action = FDIO_SPAWN_ACTION_ADD_HANDLE,
      .h = {.id = PA_HND(PA_USER0, 0), .handle = remote.release()}};

  zx::process proc;
  char err_msg_out[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
  zx_status_t res =
      fdio_spawn_etc(ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL, program_name_, args, nullptr, 1,
                     &transfer_channel_action, proc.reset_and_get_address(), err_msg_out);
  ASSERT_OK(res, "%s", err_msg_out);

  // Get our child's thread handle, but do not wait forever.
  constexpr zx::duration TIMEOUT(ZX_MSEC(2500));
  constexpr zx_signals_t WAKE_SIGS = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED;
  zx_signals_t sigs = 0;
  ASSERT_OK(local.wait_one(WAKE_SIGS, zx::deadline_after(TIMEOUT), &sigs));
  ASSERT_NE(sigs & ZX_CHANNEL_READABLE, 0);

  uint32_t rxed_handles = 0;
  ASSERT_EQ(local.read(0, nullptr, external_thread_.reset_and_get_address(), 0, 1, nullptr,
                       &rxed_handles),
            ZX_OK);
  ASSERT_EQ(rxed_handles, 1u);

  // Things went well!  Cancel our on_failure cleanup routine and Stash our
  // control channel endpoint, we will close it when it is time for our
  // external thread and process to terminate.
  control_channel_ = std::move(local);
  on_failure.cancel();
}

void ExternalThread::Stop() {
  external_thread_.reset();
  control_channel_.reset();
}
